package com.googlecode.stemwijzer.shared.serializer.rest.json;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONNull;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;

@SuppressWarnings("serial")
public abstract class AbstractJsonCodec<T> implements JsonCodec<T> {

	public static final AbstractJsonCodec<Boolean> BOOLEAN = new AbstractJsonCodec<Boolean>() {

		@Override
		public final String getIdentifier() {
			return "BOOLEAN";
		}

		@Override
		public Boolean decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			JSONBoolean b = value.isBoolean();
			if (b == null) {
				throw new DecodeException("Expected JSON boolean, instead got: " + value);
			}
			return b.booleanValue();
		}

		@Override
		public JSONValue encode(Boolean type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected boolean, got null");
			}
			return JSONBoolean.getInstance(type.booleanValue());
		}
	};

	public static final AbstractJsonCodec<Character> CHAR = new AbstractJsonCodec<Character>() {

		@Override
		public final String getIdentifier() {
			return "CHAR";
		}

		@Override
		public Character decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			return (char) toDouble(value);
		}

		@Override
		public JSONValue encode(Character type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected Character, got null");
			}
			return new JSONNumber(type);
		}
	};

	public static final AbstractJsonCodec<String> STRING = new AbstractJsonCodec<String>() {

		@Override
		public final String getIdentifier() {
			return "STRING";
		}

		@Override
		public String decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			JSONString s = value.isString();
			if (s == null) {
				throw new DecodeException("Expected JSON string, instead got: " + value);
			}
			return s.stringValue();
		}

		@Override
		public JSONValue encode(String type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected string, got null");
			}
			return new JSONString(type);
		}
	};

	public static final AbstractJsonCodec<Date> DATE = new AbstractJsonCodec<Date>() {

		@Override
		public final String getIdentifier() {
			return "DATE";
		}

		@Override
		public Date decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			return new Date((long) toDouble(value));
		}

		@Override
		public JSONValue encode(Date type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected date, got null");
			}
			return new JSONNumber((double) type.getTime());
		}
	};

	public static final AbstractJsonCodec<Short> SHORT = new AbstractJsonCodec<Short>() {

		@Override
		public final String getIdentifier() {
			return "SHORT";
		}

		@Override
		public Short decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			return (short) toDouble(value);
		}

		@Override
		public JSONValue encode(Short type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected Short, got null");
			}
			return new JSONNumber(type);
		}
	};

	public static final AbstractJsonCodec<Integer> INT = new AbstractJsonCodec<Integer>() {

		@Override
		public final String getIdentifier() {
			return "INT";
		}

		@Override
		public Integer decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			return (int) toDouble(value);
		}

		@Override
		public JSONValue encode(Integer type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected double, got null");
			}
			return new JSONNumber(type.doubleValue());
		}
	};

	public static final AbstractJsonCodec<Long> LONG = new AbstractJsonCodec<Long>() {

		@Override
		public final String getIdentifier() {
			return "LONG";
		}

		@Override
		public Long decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			return (long) toDouble(value);
		}

		@Override
		public JSONValue encode(Long type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected Long, got null");
			}
			return new JSONNumber(type);
		}
	};

	public static final AbstractJsonCodec<Float> FLOAT = new AbstractJsonCodec<Float>() {

		@Override
		public final String getIdentifier() {
			return "FLOAT";
		}

		@Override
		public Float decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			return (float) toDouble(value);
		}

		@Override
		public JSONValue encode(Float type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected Float, got null");
			}
			return new JSONNumber(type);
		}
	};

	public static final AbstractJsonCodec<Double> DOUBLE = new AbstractJsonCodec<Double>() {

		@Override
		public final String getIdentifier() {
			return "DOUBLE";
		}

		@Override
		public Double decode(JSONValue value) throws DecodeException {
			if (value == null || value.isNull() instanceof JSONNull) {
				return null;
			}
			return toDouble(value);
		}

		@Override
		public JSONValue encode(Double type) throws EncodeException {
			if (type == null) {
				throw new EncodeException("Expected Double, got null");
			}
			return new JSONNumber(type.doubleValue());
		}
	};

	protected static double toDouble(JSONValue value) throws DecodeException {
		JSONNumber n = value.isNumber();
		if (n == null) {
			throw new DecodeException("Expected JSON number, instead got: " + value);
		}
		return n.doubleValue();
	}

	private static final Map<String, JsonCodec<?>> CODECS = new HashMap<String, JsonCodec<?>>() {
		{
			put(Boolean.class.getName(), BOOLEAN);
			put(Character.class.getName(), CHAR);
			put(Date.class.getName(), DATE);
			put(Double.class.getName(), DOUBLE);
			put(Float.class.getName(), FLOAT);
			put(Integer.class.getName(), INT);
			put(Long.class.getName(), LONG);
			put(Short.class.getName(), SHORT);
			put(String.class.getName(), STRING);
		}
	};

	public static String getIdentifier(final String qualifiedTypeName) {
		if (qualifiedTypeName == null || qualifiedTypeName.isEmpty()) {
			return null;
		}

		if (CODECS.get(qualifiedTypeName) != null) {
			return CODECS.get(qualifiedTypeName).getIdentifier();
		}
		return null;
	}
}
